package org.acghub.mtdb.xmlgen.maker;

import org.acghub.mtdb.common.*;
import org.acghub.mtdb.common.type.TypeDateTime;
import org.acghub.mtdb.core.TTable;
import org.acghub.mtdb.core.Tables;
import org.acghub.mtdb.util.StringUtil;
import org.acghub.mtdb.xmlgen.Maker;

import java.io.PrintStream;
import java.time.LocalDateTime;
import java.util.List;

/**
 * @author zhouhe
 */
public class TablesMaker extends Maker<Mtdb> {

    final String className = "_Tables_";

    List<Table> getTables() {
        return this.inner().getChildren(Table.class);
    }

    List<String> getTableNames() {
        return getTables().stream().map(Table::getName).toList();
    }

    public TablesMaker(Mtdb inner, Config config) {
        super(inner, config);
    }

    @Override
    public void make() throws Exception {
        try (PrintStream ps = this.config().openTableFile(className)) {
            this.make0(ps);
        }
    }

    private void make0(PrintStream ps) {
        String packageName = this.config().tableFullPackage();
        String baseClassName = Tables.class.getCanonicalName();
        // 0.包声明
        ps.println("package " + packageName + ";");
        ps.println();
        ps.println("import com.alibaba.fastjson2.JSONObject;");
        ps.println("import org.bson.Document;");
        ps.println();
        ps.println("/**");
        ps.println("* xmlgen自动生成，不要修改此文件！！！");
        ps.println("*/");
        ps.println("public final class " + className + " extends " + baseClassName + " {");
        ps.println();
        // 1.constructor
        makeConstructor(ps, StringUtil.TAB);
        // 2.table fields
        makeTableFields(ps, StringUtil.TAB);
        // 3.table
        getTables().forEach(table -> makeTable(ps, StringUtil.TAB, table));
        ps.println("}");
    }


    private void makeConstructor(PrintStream ps, String prefix) {
        ps.println(prefix + "private " + className + "() {");
        for (String tableName : getTableNames()) {
            ps.println(prefix + StringUtil.TAB + "addTable(" + tableName + ");");
        }
        ps.println(prefix + "}");
        ps.println();
        ps.println(prefix + "public final static _Tables_ INSTANCE = new _Tables_();");
        ps.println();
    }

    private void makeTableFields(PrintStream ps, String prefix) {
        ps.println(prefix + "//================================================== table fields start ==================================================");
        for (Table table : getTables()) {
            String tableClassName = StringUtil.upperFirstChar(table.getName());
            ps.printf(prefix + "%s %s = new %s();\n", tableClassName, table.getName(), tableClassName);
        }
        ps.println(prefix + "//================================================== table fields end ==================================================");
        ps.println();
    }

    private void makeTable(PrintStream ps, String prefix, Table table) {
        String tableClassName = StringUtil.upperFirstChar(table.getName());
        String baseTableClassName = TTable.class.getCanonicalName();
        String keyType = table.getKeyType().javaInterfaceType();
        String valueType = table.getValueType().javaInterfaceType();
        if (table.getValueType() instanceof Bean) {
            valueType = config().beanFullPackage() + "." + valueType;
        }
        ps.println(prefix + String.format("static class %s extends %s<%s, %s> {", tableClassName, baseTableClassName, keyType, valueType));
        ps.println();
        // 0.name
        makeName(ps, prefix + StringUtil.TAB, table);
        // 1.newValue
        makeNewValue(ps, prefix + StringUtil.TAB, table);
        // 2.marshal
        makeMarshal(ps, prefix + StringUtil.TAB, table);
        // 3.unmarshal
        makeUnmarshal(ps, prefix + StringUtil.TAB, table);
        ps.println(prefix + "}");
        ps.println();
    }


    private void makeName(PrintStream ps, String prefix, Table table) {
        ps.println(prefix + "@Override");
        ps.println(prefix + "public String name() {");
        ps.println(prefix + StringUtil.TAB + "return " + StringUtil.quote(table.getName()) + ";");
        ps.println(prefix + "}");
        ps.println();
    }

    private void makeNewValue(PrintStream ps, String prefix, Table table) {
        String valueType = table.getValueType().javaInterfaceType();
        if (table.getValueType() instanceof Bean) {
            valueType = config().beanFullPackage() + "." + valueType;
        }
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public %s newValue() {", valueType));
        if (table.getValueType() instanceof Bean) {
            ps.println(prefix + StringUtil.TAB + "return new " + valueType + "();");
        } else if (table.getValueType() instanceof TypeDateTime) {
            ps.printf(prefix + StringUtil.TAB + "return %s.now();\n", LocalDateTime.class.getName());
        } else {
            ps.println(prefix + StringUtil.TAB + "return " + table.getValueType().getDefaultValue(null) + ";");
        }
        ps.println(prefix + "}");
        ps.println();
    }

    private void makeMarshal(PrintStream ps, String prefix, Table table) {
        String keyType = table.getKeyType().javaInterfaceType();
        String valueType = table.getValueType().javaInterfaceType();
        if (table.getValueType() instanceof Bean) {
            valueType = config().beanFullPackage() + "." + valueType;
        }
        // 1. marshalJsonKey
        ps.println(prefix + "@Override");
        ps.printf(prefix + String.format("public JSONObject marshalJsonKey(%s key) {\n", keyType));
        ps.printf(prefix + StringUtil.TAB + "return new JSONObject().fluentPut(%s, %s);\n", StringUtil.quote("_id"), "key");
        ps.println(prefix + "}");
        ps.println();

        // 2. marshalJsonValue
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public JSONObject marshalJsonValue(%s value) {", valueType));
        if (table.getValueType() instanceof Bean) {
            ps.println(prefix + StringUtil.TAB + "return value.marshalJson();");
        } else if (table.getValueType() instanceof TypeDateTime) {
            ps.printf(prefix + StringUtil.TAB + "return new JSONObject().fluentPut(%s, %s.parse(%s));\n", StringUtil.quote("value"), TypeDateTime.class.getName(), "value");
        } else {
            ps.println(prefix + StringUtil.TAB + "return new JSONObject().fluentPut(" + StringUtil.quote("value") + ", value);");
        }
        ps.println(prefix + "}");
        ps.println();

        // 3. marshalDocumentKey
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public Document marshalDocumentKey(%s key) {", keyType));
        ps.printf(prefix + StringUtil.TAB + "return new Document(%s, %s);\n", StringUtil.quote("_id"), "key");
        ps.println(prefix + "}");
        ps.println();

        // 4. marshalDocumentValue
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public Document marshalDocumentValue(%s value) {", valueType));
        if (table.getValueType() instanceof Bean) {
            ps.println(prefix + StringUtil.TAB + "return value.marshalDocument();");
        } else if (table.getValueType() instanceof TypeDateTime) {
            ps.printf(prefix + StringUtil.TAB + "return new Document(%s, %s.parse(%s));\n", StringUtil.quote("value"), TypeDateTime.class.getName(), "value");
        } else {
            ps.println(prefix + StringUtil.TAB + "return new Document(" + StringUtil.quote("value") + ", value);");
        }
        ps.println(prefix + "}");
        ps.println();
    }

    private void makeUnmarshal(PrintStream ps, String prefix, Table table) {
        String keyType = table.getKeyType().javaInterfaceType();
        String valueType = table.getValueType().javaInterfaceType();
        if (table.getValueType() instanceof Bean) {
            valueType = config().beanFullPackage() + "." + valueType;
        }
        // 1.unmarshalJsonKey
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public %s unmarshalJsonKey(JSONObject reader) {", keyType));
        ps.printf(prefix + StringUtil.TAB + "return reader.get%s(%s);\n", keyType, StringUtil.quote("_id"));
        ps.println(prefix + "}");
        ps.println();

        // 2. unmarshalJsonValue
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public %s unmarshalJsonValue(JSONObject reader) {", valueType));
        if (table.getValueType() instanceof Bean) {
            ps.printf(prefix + StringUtil.TAB + "%s v = newValue();\n", valueType);
            ps.println(prefix + StringUtil.TAB + "v.unmarshal(reader);");
            ps.println(prefix + StringUtil.TAB + "return v;");
        } else if (table.getValueType() instanceof TypeDateTime) {
            ps.printf(prefix + StringUtil.TAB + "return %s.parse(reader.getString(%s));\n", TypeDateTime.class.getName(), StringUtil.quote("value"));
        } else {
            ps.printf(prefix + StringUtil.TAB + "return reader.get%s(%s);\n", valueType, StringUtil.quote("value"));
        }
        ps.println(prefix + "}");
        ps.println();

        // 3. unmarshalDocumentKey
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public %s unmarshalDocumentKey(Document reader) {", keyType));
        ps.printf(prefix + StringUtil.TAB + "return %s(reader, %s, %s.class);\n", MarshalUtil.GET, StringUtil.quote("_id"), keyType);
        ps.println(prefix + "}");
        ps.println();

        // 4.unmarshalDocumentValue
        ps.println(prefix + "@Override");
        ps.println(prefix + String.format("public %s unmarshalDocumentValue(Document reader) {", valueType));
        if (table.getValueType() instanceof Bean) {
            ps.printf(prefix + StringUtil.TAB + "%s v = newValue();\n", valueType);
            ps.println(prefix + StringUtil.TAB + "v.unmarshal(reader);");
            ps.println(prefix + StringUtil.TAB + "return v;");
        } else {
            ps.printf(prefix + StringUtil.TAB + "return %s(reader, %s, %s.class);\n", MarshalUtil.GET, StringUtil.quote("value"), valueType);
        }
        ps.println(prefix + "}");
        ps.println();
    }
}
